#include "autoconf.h"
#include "libradius.h"

#include <sys/file.h>

#ifdef HAVE_NETINET_IN_H
#	include <netinet/in.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>

#ifdef HAVE_UNISTD_H
#	include <unistd.h>
#endif

#include <signal.h>

#ifdef HAVE_GETOPT_H
#	include <getopt.h>
#endif

#ifdef HAVE_SYS_SELECT_H
#	include <sys/select.h>
#endif

#ifdef HAVE_SYSLOG_H
#	include <syslog.h>
#endif

#ifdef HAVE_SYS_WAIT_H
#	include <sys/wait.h>
#endif

#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/socket.h>
#include "utils.h"
#include "config.h"
#include "output.h"

//!< secret, shared with server.c
extern const char *secret;

//!
pthread_cond_t server_done;
pthread_mutex_t server_mutex;

extern const char *packet_codes[];

static int thread_count = 2;

float reject_per = 0.0;
float drop_per = 0.0;

static pthread_mutex_t count_mutex;		//!< protect TOTAL_LOST, TOTAL_PKTS
extern unsigned int statis[CLASS_END][TYPE_END];





RADIUS_PACKET *generate_reply(RADIUS_PACKET *packet)
{
	VALUE_PAIR *vp;
	assert(packet);
	RADIUS_PACKET *reply;

	reply = malloc(sizeof(*reply));
	if (!reply)
	{
		ERROR("server reply malloc error\n");
		exit(0);
	}
	memcpy(reply, packet, sizeof(*reply));
	reply->data = NULL;
	reply->vps = NULL;

	//!<lock
	pthread_mutex_lock(&count_mutex);
	statis[_SERVER][TOTAL_PKTS]++;
	pthread_mutex_unlock(&count_mutex);

	reply->src_port = packet->dst_port;
	reply->dst_port = packet->src_port;
	
	
	reply->src_ipaddr = packet->dst_ipaddr;
	reply->dst_ipaddr = packet->src_ipaddr;

	switch(reply->code)
	{
	case PW_AUTHENTICATION_REQUEST:
		statis[_SERVER][TOTAL_AUTH]++;
		if((float)statis[_SERVER][TOTAL_REJECT] / (float)statis[_SERVER][TOTAL_AUTH] < reject_per)
		{
			reply->code = PW_AUTHENTICATION_REJECT;
			statis[_SERVER][TOTAL_REJECT]++;
		}
		else
			reply->code = PW_AUTHENTICATION_ACK;
		break;
					
	case PW_ACCOUNTING_REQUEST:
		reply->code = PW_ACCOUNTING_RESPONSE;
		break;
	default:
		DBG("unknown reply type");
		break;
	}

	pthread_mutex_lock(&count_mutex);
	if ((float)statis[_SERVER][TOTAL_LOST] / (float)statis[_SERVER][TOTAL_PKTS] < drop_per)
	{
		statis[_SERVER][TOTAL_LOST]++;
		free(reply);
		reply = NULL;
	}
	pthread_mutex_unlock(&count_mutex);

	return reply;

}
static void dump_recv_packet(RADIUS_PACKET *packet, FILE *fp)
{
	char ip_buffer[128];
	VALUE_PAIR *vp;
	assert(packet && fp);

	fprintf(fp, "server recv %s of id %d from %s port %d\n", 
				packet_codes[packet->code], packet->id,
				ip_ntoa(ip_buffer, packet->src_ipaddr),
				packet->src_port);

	for (vp = packet->vps; vp; vp = vp->next)
	{
		putchar('\t');
		vp_print(fp, vp);
		putchar('\n');
	}

}


static void dump_send_packet(RADIUS_PACKET *packet, FILE *fp)
{
	char ip_buffer[128];
	VALUE_PAIR *vp;
	assert(packet && fp);

	fprintf(fp, "server send %s of id %d from %s port %d\n", 
				packet_codes[packet->code], packet->id,
				ip_ntoa(ip_buffer, packet->dst_ipaddr),
				packet->dst_port);

	for (vp = packet->vps; vp; vp = vp->next)
	{
		putchar('\t');
		vp_print(fp, vp);
		putchar('\n');
	}

}

static void *reply_thread(void *arg)
{
	struct sockaddr_in sa;
	int sockfd;
	fd_set readfds;
	RADIUS_PACKET *packet, *reply;
	int port = *(int*)arg;
	VALUE_PAIR *vp;
	

	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) /* create a socket */
	{
		ERROR("server auth sockfd create error\n");
		exit(1);
	}

	memset (&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = htonl(INADDR_ANY);
	sa.sin_port = htons(port);

	if(bind(sockfd, (struct sockaddr*)&sa, sizeof(sa)) < 0)
	{
		ERROR("bind auth sockfd error\n");
		exit(1);
	}

	FD_ZERO(&readfds);
	FD_SET(sockfd, &readfds);
	int ret;
	struct timeval tv;
	tv.tv_sec = 1;
	tv.tv_usec = 0;

	pthread_mutex_lock(&count_mutex);
	thread_count --;		//
	DBG("%d\n", thread_count);
	if(thread_count > 0)
	{
		pthread_mutex_unlock(&count_mutex);
		DBG("wait for brother thread\n");
		pthread_mutex_lock(&server_mutex);
		pthread_cond_wait(&server_done, &server_mutex);
		pthread_mutex_unlock(&server_mutex);
		DBG("received broad cast\n");
	}
	else
	{
		pthread_mutex_unlock(&count_mutex);
		DBG("all thread have done\n");
		pthread_cond_broadcast(&server_done);
	}

	
	while(1)
	{
		FD_ZERO(&readfds);
		FD_SET(sockfd, &readfds);
		ret = select(sockfd + 1, &readfds, NULL, NULL, &tv);
		if( ret < 0)
		{
			LOG("select error");
			continue;
		}
		else if(ret != 1)
		{
			//DBG("server timeout(%d)\n", port);
			continue;
		}

		packet = rad_recv(sockfd);
		if (!packet)
		{
			LOG("recv error\n");
			continue;
		}

		//rad_decode(packet, NULL, secret);
		//dump_recv_packet(packet, stdout);
		reply = generate_reply(packet);		//!< reply = NULL, maybe
		//dump_send_packet(reply, stdout);

		//if(packet->data)
		//	free(packet->data);
		//packet->data = NULL;   //!< must be set to zero
	
		
		
		//DBG("code:%d id:%d dst_port:%d dst_ipaddr:%d\n", packet->code, packet->id, packet->dst_port, packet->dst_ipaddr);
		if(reply && (rad_send(reply, packet, secret) < 0))
		//if(reply && (rad_send(reply, packet, "123453") < 0))
			ERROR("rad send error\n");
		//else
			//DBG("send success\n");
		rad_free(&packet);
		free(reply);

	}
//!< some 
	close(sockfd);
	ERROR("auth thread exit\n");
	
}


void *radserver(void *arg)
{
	pthread_t auth, acct;
	int auth_port = PW_AUTH_UDP_PORT;
	int acct_port = PW_ACCT_UDP_PORT;

	if(pthread_mutex_init(&count_mutex, NULL))
	{
		ERROR("rad server count mutex init error\n");
		exit(0);
	}
	if (pthread_create(&auth, NULL, reply_thread, &auth_port))
	{
		ERROR("auth thread create error\n");
		exit(1);
	}
	if (pthread_create(&acct, NULL, reply_thread, &acct_port))
	{
		ERROR("acct thread create error\n");
		exit(1);
	}


	pthread_join(auth, NULL);
	pthread_join(acct, NULL);

	pthread_mutex_destroy(&count_mutex);

}
